module Explode.Stage;
private import Graphics.IGraphics;
private import Sound.Manager;
private import Explode.Characters;
private import Explode.GameConfig;
private import Explode.Player;
private import Explode.Enemy;
private import Explode.MovableBlock;
private import Explode.ScoreBoard;
private import Explode.Cursor;
private import System.Serializer;
private import Input.InputRecorder;
private import Input.GamePad;
private import Input.Mouse;
private import std.string;
private import std.uri;
private import std.math;
private import std.random;
private import System.Console;
private import std.file;

public final class Blocks {
	public static const int WIDTH       = 16;
	public static const int HEIGHT      = 16;
	public static const int HALFWIDTH   =  8;
	public static const int HALFHEIGHT  =  8;

	public static const int SHIFTX  =  4;
	public static const int SHIFTY  =  4;
	
	public static const int COUNT  = 9;

	public static const int NUM_BLANK   = 0;
	public static const int NUM_BRICK   = 1;
	public static const int NUM_ROCK    = 2;
	public static const int NUM_LADDER  = 3;
	public static const int NUM_ROPE    = 4;
	public static const int NUM_HHOLE   = 5;
	public static const int NUM_HLADDER = 6;
	public static const int NUM_GOLD    = 7;
	public static const int NUM_ENEMY   = 8;
	public static const int NUM_PLAYER  = 9;

	public static const int NUM_GOAL    = 10;
	public static const int NUM_MBLOCK  = 11;
	public static const int NUM_HENEMY  = 12;
	public static const int NUM_BOMBER  = 13;
	public static const int NUM_BONUS   = 20;

	public static const int NUM_PLMASK  = 29;
	public static const int NUM_GHOST   = 30;

	public static const int BLANK   = 0 ;
	public static const int BRICK   = 1 << (  1 - 1 ) ;
	public static const int ROCK    = 1 << (  2 - 1 ) ;
	public static const int LADDER  = 1 << (  3 - 1 ) ;
	public static const int ROPE    = 1 << (  4 - 1 ) ;
	public static const int HHOLE   = 1 << (  5 - 1 ) ;
	public static const int HLADDER = 1 << (  6 - 1 ) ;
	public static const int GOLD    = 1 << (  7 - 1 ) ;
	public static const int ENEMY   = 1 << (  8 - 1 ) ;
	public static const int PLAYER  = 1 << (  9 - 1 ) ;

	public static const int GOAL    = 1 << ( 10 - 1 ) ;
	public static const int MBLOCK  = 1 << ( 11 - 1 ) ;
	public static const int HENEMY  = 1 << ( 12 - 1 ) ;
	public static const int BOMBER  = 1 << ( 13 - 1 ) ;
		public static const int BLOCK = BRICK | ROCK;

	public static const int DIGGER  = 1 << ( 16 - 1 );

	public static const int BONUS   = 1 << ( 20 - 1 );
	public static const int PLMASK  = 1 << ( 29 - 1 );
	public static const int GHOST   = 1 << ( 30 - 1 );


	public static bool isCeiling( int b ) {
		return  isMatch( b , BLOCK|HHOLE|MBLOCK );
	} //

	public static bool isFloor( int b ) {
		return  isMatch( b , BLOCK|LADDER|ENEMY|MBLOCK );
	} //

	public static bool isWall( int b ) {
		return  isMatch( b , BLOCK|HHOLE|MBLOCK );
	} //

	public static bool isBlank( int b ) {
		return  ( b & ~( BLANK | HLADDER | DIGGER | PLAYER | GHOST | PLMASK) ) == 0 ;
	} //

	public static bool isNoBlock( int b ) {
		return  ( b & ~(HLADDER | PLAYER | GHOST | PLMASK) ) == 0;
	} //

	public static bool isBrick( int b ) {
		return  isMatch( b , BRICK );
	} //
	public static bool isRock( int b ) {
		return  isMatch( b , ROCK );
	} //
	public static bool isLadder( int b ) {
		return  isMatch( b , LADDER );
	} //
	public static bool isRope( int b ) {
		return  isMatch( b , ROPE );
	} //

	public static bool isHHole( int b ) {
		return  isMatch( b , HHOLE );
	} //
	public static bool isHLadder( int b ) {
		return  isMatch( b , HLADDER );
	} //
	public static bool isGold( int b ) {
		return  isMatch( b , GOLD );
	} //

	public static bool isEnemy( int b ) {
		return  isMatch( b , ENEMY );
	} //

	public static bool isGoal( int b ) {
		return  isMatch( b , GOAL );
	} //

	public static bool isDigger( int b ) {
		return  isMatch( b , DIGGER );
	} //

	public static bool isBlock( int b ) {
		return  isMatch( b , BLOCK );
	} //

	public static bool isFloorR( int b ) {
		return  isMatch( b , BLOCK|LADDER );
	} //
	public static bool isFloorB( int b ) {
		return  isMatch( b , BLOCK );
	} //

	public static bool isLadderOrRope( int b ) {
		return  isMatch( b , LADDER|ROPE );
	} //

	public static bool isAnything( int b ) {
		return  isMatch( b , BLOCK|LADDER|ROPE|GOLD|GOAL|DIGGER|ENEMY|BOMBER|BONUS|MBLOCK );
	} //

	public static bool isAnyFloor( int b ) {
		return  isMatch( b , BLOCK|LADDER|DIGGER|BOMBER|MBLOCK|ENEMY );
	} //

	
	public static bool isMatch( int b , int mask ) {
		return ( b & mask ) != 0;
	} //
	
	public static int imageIndex( int block ) {
		int ret = 0;
		switch( block ) {
			case BRICK:
				ret = 1;
				break;
			case ROCK:
				ret = 2;
				break;
			case LADDER:
				ret = 3;
				break;
			case ROPE:
				ret = 4;
				break;
			case HHOLE:
				ret = 5;
				break;
			case HHOLE | PLMASK:
				ret = 5;
				break;
			case HLADDER:
				ret = 6;
				break;
			case GOLD:
				ret = 7;
				break;
			case ENEMY:
				ret = 8;
				break;
			case PLAYER:
				ret = 9;
				break;
			case GOAL:
				ret = 10;
				break;
			case MBLOCK:
				ret = 11;
				break;
			case BRICK | DIGGER:
				ret = 0;
				break;
			default:
				break;
		}
		return ret;
	} //

	public static string wCharToChar( string line ) {
		string[] wc = ["×","田","■","＃","－","凹","己","▲","員","♀","門","□","囚"];
		string[] c  = [ "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C"];
		line = strip( line );
		line = replace( line, "｜", ""); // "”│\|]([ 0-9a-f]+)["”│\|
		line = replace( line,  "|", "");
		line = replace( line, "│", "");
		foreach ( int i , string x ; wc ) {
			line = replace( line , x , c[i] );
		}
		return line;
	} //

} //

private class Randomizer {
	private int _rnd ;//= seed;
	private int _rndA = 8 * 5 + 5;
	private int _rndC = 1213;
	private int _rndM = 0x10000;
	private int _seed = 0;
	public this( int seed ) {
		_seed = abs( seed % 0x10000 );
		_rnd = seed ;
	} //
	public int seed() {
		return _seed;
	} //
	private int get(){
		_rnd = ( _rndA * _rnd + _rndC ) % _rndM;
		return abs( _rnd );
	} //
	private void set( int seed ) {
		_seed = seed;
		_rnd = seed;
	}
} //


public abstract class BaseStage  : IDrawingEntry {//, ISerializer{
	protected int _sysver = GameConfig.SYSVER;
	protected int _cols = 15 , _rows = 15;
	protected int[][] _dat;
	protected string _datStr;

	protected ITexture _texture ;


	protected IGraphics _graphics ;

	
	protected Randomizer _random;
	protected int _frameCnt = 0 ;

	protected string _title = "Explode Runner";
	protected string _cpu   = "NORMAL";

	protected int _startX = 0 , _startY = 0 , _width = 0 , _height = 0
				,_vWidth = 0 , _vHeight= 0 , _scrollTop = 0 , _scrollLeft = 0
				,_vWidthMax=0,_vHeightMax=0
				,_scrollTopMin = 0 , _scrollLeftMin = 0
				,_scrollTopMax = 0 , _scrollLeftMax = 0;
	protected ScoreBoard _scoreBoard;
	protected BackgroundText _bgText;

	protected static string[ string ] _cfg;

	protected string _mapTexture ;
	protected string _playerTexture;
	protected string _enemyTexture;
	protected string _missSound;
	protected string _digSound;
	protected string _goalSound;
	protected string _ladderSound;
	protected string _goldSound;
	protected string _gameBgm;

	public string mapTextureName()	 {return _mapTexture;}
	public string playerTextureName(){return _playerTexture;}
	public string enemyTextureName() {return _enemyTexture;}
	public string missSound() {return _missSound;}
	public string digSound() {return _digSound;}
	public string goalSound() {return _goalSound;}
	public string ladderSound() {return _ladderSound;}
	public string goldSound() {return _goldSound;}
	public string gameBgm() { return _gameBgm;}

	public int sysver(){ return _sysver;}
	public ITexture texture(){ return _texture;}
	public int  random() {return _random.get(); }
	public string cpu(){return _cpu;}
	public int frameCnt() {return _frameCnt;}
	public int scrollTop() {return _scrollTop;}
	public int scrollLeft(){return _scrollLeft;}
	public int rows(){return _rows;}
	public int cols(){return _cols;}
	public float x(){return 0;}
	public float y(){return 0;}
	public float z(){return 0;}
	public Player player() {return null;}

	public string mapData() {
		return _datStr;
	} //
	public string title(){return _title;}
	public void   title( string s ) { _title=s;}

	public this(){}


	public this( IGraphics g) {
		_graphics = g;

		initImage( );
		_datStr = "";
	}

	public bool drawingEntry( IGraphics g ) {
		g.addDrawingEntry( this );
		return true;
	} //
	
	public void setViewport( IGraphics g ) {
		g.setViewport(	  _scrollLeft
						, _scrollTop
						, _vWidth  + _scrollLeft
						, _vHeight + _scrollTop
						);
	} //
	
	public bool draw( IGraphics g ) {
		alias Blocks B;
		float clipX  = g.viewrect.x  , clipY  = g.viewrect.y , 
			  clipX2 = g.viewrect.x2 , clipY2 = g.viewrect.y2;

		setViewport( g );

		int vw = _vWidth;
		int vh = _vHeight;
		int sx = _scrollLeft >> B.SHIFTX;
		int sy = _scrollTop  >> B.SHIFTY;
		int dw = vw / B.WIDTH  + sx + 2;
		int dh = vh / B.HEIGHT + sy + 2;
		
		if ( dw > _cols ) dw = _cols;
		if ( dh > _rows ) dh = _rows;
		if ( sx < 0 ) sx = 0;
		if ( sy < 0 ) sy = 0;
		for ( int y = sy ; y < dh ; y++ ) {
			for ( int x = sx ; x < dw ; x++ ) {
				if ( B.isDigger( _dat[y][x] ) ) continue;
				int imgIndex = B.imageIndex( _dat[y][x] );
				if ( imgIndex != 0 ) {
					_texture.frameIndex = imgIndex * 4  + ( ( _frameCnt / 32 ) % 4 ) ;
					float drawX = x * B.WIDTH ;
					float drawY = y * B.HEIGHT;
					g.drawImage( _texture , drawX , drawY , B.WIDTH , B.HEIGHT );
				}
			}
		}

		g.setViewport( clipX , clipY , clipX2 , clipY2);
		return true;
	} //
	


	protected bool initImage( ) {
		_texture = _graphics.createTexture("MAPCHIP" ,getParam( "mapImg"    , GameConfig.mapTexture    ) , 0 , 0 );
		_texture.setFrameSize( 32,32 );
		return true;
	} //



	public bool action() {
		_frameCnt++;
		return true;
	}

	protected void reset() {
		_cols = 15;
		_rows = 15;
		_dat = null;
		_datStr  = "";
		_frameCnt = 0;
		string[ string ] cfg;
		_rpCode = "";
		_cfg = cfg;
	} //

	protected int ctoi( char c ) {
		int cc = cast(int) c;
		if( cc >= 'A' && cc <= 'Z' ) return cc - cast(int)'A' + 10;
		if( cc >= 'a' && cc <= 'z' ) return cc - cast(int)'a' + 10;
		if( cc >= '0' && cc <= '9' ) return cc - cast(int)'0';
		return 0;
	} //

	public bool loadStageFromFile( string src ) {
		if ( !exists( src ) ) {
			return false;
		}
		string dat = cast(string)std.file.read( src );
		return loadStage( dat );
	} //

	protected string _rpCode;
	public bool loadStage( string text ) {
		reset();
		string[] tmp = split( text , "\n" );
		string[] dat;// = new Array();

		bool rpFlag   = false , paramFlag = false , bodyFlag = false , headFlag = true;

		int zerocode = cast(int)'0';

		_rows = 0;
		_cols = 0;


		foreach( int i , string ln ; tmp ) {
			ln = strip( ln );
			if ( ln.length == 0 || ln[0] == '#' ) continue;

			if ( find( ln , "[/replay]" ) >= 0 ) {
				rpFlag = false;
				continue;
			} else
			if ( find( ln , "[replay]" ) >= 0 ) {
				rpFlag = true;
				paramFlag = false; bodyFlag    = false; headFlag  = false;
				continue;
			} else
			if ( find( ln , "[/body]" ) >= 0 ) {
				bodyFlag = false;
				continue;
			} else
			if ( find( ln , "[body]" ) >= 0 ) {
				bodyFlag = true;
				paramFlag = false; rpFlag    = false; headFlag  = false;
				continue;
			} else
			if ( find( ln , "[/param]" ) >= 0 ) {
				paramFlag = false;
				continue;
			} else
			if ( find( ln , "[param]"  ) >= 0 ) {
				paramFlag = true;
				bodyFlag = false; rpFlag   = false;	headFlag = false;
				continue;
			}

			if ( rpFlag ) {
				_rpCode ~= ln ~ "\n";
			} else
			if ( bodyFlag  && ln.length > 3 ) {
				ln = Blocks.wCharToChar( ln );
				if ( ln.length < 3 ) continue;

				if ( dat.length == 0 ) dat ~= [];
				_datStr ~= ln ~ "\n";
				_rows++;

				string arr = ['2'];
				for ( int j = 0 ; j < ln.length ; j++ ) {
					char c = ln[j];
					arr ~= c;
				}
				arr ~= '2';

				dat ~= arr;
				_cols = ( _cols >= arr.length ) ? _cols : arr.length;
			} else
			if ( paramFlag ) {
				int eq = find( ln , "=" );
				if ( eq <= 0 ) continue;
				string[] nmvl = split( ln , "=" );
				string nm = strip( nmvl[0] );
				string vl = strip( nmvl[1] );
				setParam( nm , vl);
			}
		}
		_rows = _rows + 2;

		_dat.length = 0;
		_dat ~= [Blocks.ROCK];
		for ( int i = 0 ; i < _rows - 2 ; i++ ) {
			int[] ln = [];
			for ( int j = 0 ; j < _cols ; j++ ) {
				ln ~= ( 1 << ctoi( dat[i][j] ) ) >> 1;
			}
			_dat ~= ln;
		}
		_dat ~= [Blocks.ROCK];
		for ( int j = 1 ; j < _cols ; j++ ) {
			_dat[ 0 ]		   ~= Blocks.ROCK;
			_dat[ _rows - 1 ]  ~= Blocks.ROCK;
		}

		_width    = _cols * Blocks.WIDTH;
		_height   = _rows * Blocks.HEIGHT;

		string mp	   = getParam( "mapImg"    , GameConfig.mapTexture    );
		_playerTexture = getParam( "playerImg" , GameConfig.playerTexture );
		_enemyTexture  = getParam( "enemyImg"  , GameConfig.enemyTexture  );
		if ( mp != _mapTexture ) {
			_mapTexture = mp;
			initImage( );
		}

		_vWidth   = _width  - Blocks.WIDTH;
		_vHeight  = _height - Blocks.HEIGHT;
		_vWidthMax  = getParam( "vWidthMax"   , _vWidth );
		_vHeightMax = getParam( "vHeightMax"  , _vHeight );
		int gw = _graphics.screenWidth , gh = _graphics.screenHeight;



	


		return true;
	} //

	public int getXYBlock( int xx , int yy ) {
		try{
			if ( yy < 0 ) return Blocks.BLANK;
			return _dat[yy][xx];
		}catch{
			return 0;
		}
	} //
	
	public int getXYPointBlock( int xx , int yy ){
		return getXYBlock( xx >> Blocks.SHIFTX , yy >> Blocks.SHIFTY );
	} //

	public void setXYBlock( int xx, int yy , int block ) {
		try{
			if ( xx < 1 || yy < 0 || xx >= _cols-1 || yy >= _rows-1 ) return;
			_dat[yy][xx] = block;
		}catch{
			Console.writeln("setXYBlock access Exeption! %d,%d" , xx , yy );
		}
	} //
	
	public void addXYBlockMask( int xx , int yy , int mask ) {
		try{
		if ( xx < 1 || yy < 0 || xx >= _cols-1 || yy >= _rows-1 ) return;
			_dat[yy][xx]  |= mask;
		}catch{
			Console.writeln("addXYBlock access Exeption! %d,%d" , xx , yy );
		}
	} //

	public void subXYBlockMask( int xx , int yy , int mask ) {
		try{
		if ( xx < 1 || yy < 0 || xx >= _cols-1 || yy >= _rows-1 ) return;
		_dat[yy][xx]  &= ~mask;
		}catch{
			Console.writeln("subXYBlock access Exeption! %d,%d" , xx , yy );
		}
	} //



	public void setScrollPoint( IDrawingEntry p ) {
		if ( p is null ) return;
		int vw = _vWidth; 
		int vh = _vHeight;
		int pcx = cast(int)( p.x + Blocks.HALFWIDTH );
		int pcy = cast(int)( p.y + Blocks.HALFHEIGHT );
		setScrollPoint( pcx - ( vw / 2 ) , pcy - ( vh / 2 ) );
	} //

	public void setScrollPoint( int sx , int sy ){
		int vw = _vWidth; 
		int vh = _vHeight;
		if( vw < _width ) {
			_scrollLeft = sx;
			if ( _scrollLeft > _scrollLeftMax ) _scrollLeft = _scrollLeftMax;
			if ( _scrollLeft < _scrollLeftMin ) _scrollLeft = _scrollLeftMin;
		}else{
			_scrollLeft = ( ( _width - vw ) / 2 );
		}

		if ( vh < _height ) {
			_scrollTop  = sy;
			if ( _scrollTop  > _scrollTopMax ) _scrollTop  = _scrollTopMax;
			if ( _scrollTop  < _scrollTopMin ) _scrollTop  = _scrollTopMin;
		} else {
			_scrollTop = ( ( _height - vh ) / 2 );
		}
	} //




	public void setParam( string name , string value ) {
		_cfg[ name ] = value;
	} //
	public void setParam( string name , int value ) {
		_cfg[ name ] = std.string.toString( value );
	} //
	public void setParam( string name , float value ) {
		_cfg[ name ] = std.string.toString( value );
	} //

	public string getParam( string name , string defValue ) {
		if ( name in _cfg ) return _cfg[ name ];
							 return defValue;
	} //
	public int    getParam( string name , int defValue    ) {
		if ( name in _cfg ) return cast(int)atoi( _cfg[ name ] );
		else				 return defValue;
	} //
	public float  getParam( string name , float defValue  ) {
		if ( name in _cfg ) return atof( _cfg[ name ] );
		else				 return defValue;
	} //

	public abstract void zoomIn();
	public abstract void zoomOut();
	
} //

public class GameStage : BaseStage {
	public static const int MODE_INIT     =  0;
	public static const int MODE_GAME     =  1;
	public static const int MODE_CLEAR    =  2;
	public static const int MODE_DEAD     =  4;
	public static const int MODE_GIVEUP   =  8;
	public static const int MODE_REPLAY   = 16;
	public static const int MODE_GAMEOVER = 32;
	protected int _mode = MODE_INIT;
	protected int _goldCnt = 0 , _giveupTime = -1;
	protected int _killCnt = 0 , _diggCnt = 0 , _enemyCnt = 0 , _mblockCnt = 0;

	protected Player _player;
	protected string _playerName = "RUNNER" ;

	protected GameParam _param;

	public Player player(){return _player;}
	public string playerName(){return _playerName;}
	protected InputRecorder _recorder;
	protected IGamePad _pad;
	protected BasicMouse _mouse;
	protected bool _replayEnable = true;

	public int mode() {return _mode;}
	public int goldCnt() {return _goldCnt;}
	public int killCnt() {return _killCnt;}
	public void killCnt( int n ){ _killCnt = n; }

	protected CharacterGroup _enemies,_diggers,_mblocks,_others;
	protected EditCursor _cursor;

	public EditCursor cursor() { return _cursor; }

	// ゴールの出現する場所を格納する配列
	protected int[][] _goalPoint;


	public void  gamepad( IGamePad g ) {
		_pad = g;
	} //
	public void  mouse( BasicMouse m ) {
		_mouse = m;
	} //

	public void  setGiveup() {
		if( _mode == MODE_GAME ) _giveupTime = _frameCnt + 1;
	} //


	public this( IGraphics g , GameParam p){
		super( g );
		_graphics = g;
		_param	  = p;

		initImage( );
		initObject( g , p );
	} //
	
	protected void initObject( IGraphics g , GameParam p ) {
		_mapTexture		= GameConfig.mapTexture;
		_playerTexture	= GameConfig.playerTexture;
		_enemyTexture	= GameConfig.enemyTexture;
		_missSound		= GameConfig.missSound;
		_digSound		= GameConfig.digSound;
		_goalSound		= GameConfig.goalSound;
		_goldSound		= GameConfig.goldSound;
		_ladderSound	= GameConfig.ladderSound;
		_gameBgm		= GameConfig.gameBgm;

		_random  = new Randomizer( 1024 );
		_enemies = new CharacterGroup( 2048 );
		_mblocks = new CharacterGroup( 2048 );
		_diggers = new CharacterGroup( 2048 );
		_others  = new CharacterGroup( 2048 );

		_enemies.groupDrawing = true;
		_mblocks.groupDrawing = true;
		_diggers.groupDrawing = true;

		_recorder = new InputRecorder();
		_scoreBoard = new ScoreBoard( this , p );
		_bgText     = new BackgroundText( this , p );
		_cursor  = new EditCursor( this , g );
		_datStr = "";
	} //




	public bool drawingEntry( IGraphics g ) {
		g.addDrawingEntry( this );
		_player.drawingEntry( g );
		_enemies.drawingEntry( g );
		_mblocks.drawingEntry( g );
		_diggers.drawingEntry( g );
		_others.drawingEntry( g );
		_scoreBoard.drawingEntry( g );
		_bgText.drawingEntry( g );
		if ( _mode != MODE_REPLAY ) {
			_cursor.drawingEntry( g );
		}
		return true;
	} //

	public bool draw( IGraphics g ) {
		alias Blocks B;
		float clipX  = g.viewrect.x  , clipY  = g.viewrect.y , 
			  clipX2 = g.viewrect.x2 , clipY2 = g.viewrect.y2;

		setViewport( g );

		int vw = _vWidth;
		int vh = _vHeight;
		int sx = _scrollLeft >> B.SHIFTX;
		int sy = _scrollTop  >> B.SHIFTY;
		int dw = vw / B.WIDTH  + sx + 2;
		int dh = vh / B.HEIGHT + sy + 2;
		
		if ( dw > _cols ) dw = _cols;
		if ( dh > _rows ) dh = _rows;
		if ( sx < 0 ) sx = 0;
		if ( sy < 0 ) sy = 0;
		for ( int y = sy ; y < dh ; y++ ) {
			for ( int x = sx ; x < dw ; x++ ) {
				if ( B.isDigger( _dat[y][x] ) ) continue;
				int imgIndex = B.imageIndex( _dat[y][x] & ~(B.ENEMY|B.PLAYER|B.HLADDER|B.MBLOCK|B.GHOST) );
				if ( imgIndex != 0 ) {
					if ( imgIndex == B.NUM_HHOLE && ( _dat[y][x] & B.PLMASK ) == 0) imgIndex = B.NUM_BRICK; // 落とし穴を隠す
					_texture.frameIndex = imgIndex * 4  + ( ( _frameCnt / 32 ) % 4 ) ;
					float drawX = x * B.WIDTH ;
					float drawY = y * B.HEIGHT;
					g.drawImage( _texture , drawX , drawY , B.WIDTH , B.HEIGHT );
				}
			}
		}

		g.setViewport( clipX , clipY , clipX2 , clipY2);
		return true;
	} //

	public bool action() {
		_frameCnt++;
		int padState;
		if ( _mode == MODE_REPLAY ) {
			padState = _recorder.play( _pad );
			if ( !_recorder.hasNext ) _mode = MODE_GAME;
		} else {
			padState = _recorder.record( _pad );
		}

		_player.padStatus = padState;
		if ( _mode != MODE_REPLAY ) {
			cursorAction();
		}

		_diggers.action();
		_player.action();
		_enemies.action();
		_mblocks.action();
		 _diggers.collision(_enemies);
		 _diggers.collision(_mblocks);
		 _diggers.collision(_player);
		 _enemies.collision(_player);
		 _enemies.collision(_mblocks);
		 _mblocks.collision(_player);

		_others.action();
		
		_scoreBoard.action();
		_bgText.action();


		super.setScrollPoint( _player );

		if ( _frameCnt == _giveupTime ) _player.giveup();


		if ( _player.isDead ) {
			_mode = MODE_DEAD;
			return false;
		} else
		if ( _player.goalFlag 
			|| ( _goalPoint.length == 0 && _goldCnt <= 0 && _player.y <= Blocks.HEIGHT) ) {
			_mode = MODE_CLEAR;
			return false;
		}
		return true;
	} //

	protected void cursorAction() {
		alias Blocks B;
		alias _graphics g;
		_cursor.action();
		if ( _mouse.motionX != 0 || _mouse.motionY != 0 ) {
			float xp = 1f * _mouse.x / g.screenWidth;
			float yp = 1f * _mouse.y / g.screenHeight;
			float mx = _vWidth  * xp + _scrollLeft;
			float my = _vHeight * yp + _scrollTop;
			int xx = ( cast(int)round(mx) >> B.SHIFTX ) << B.SHIFTX;
			int yy = ( cast(int)round(my) >> B.SHIFTY ) << B.SHIFTY;

			_cursor.moveTo( xx , yy );
		}
		int dy = _mouse.deltaY;
		if ( dy != 0 ) {
			if ( dy < 0 ) _cursor.setNextBlock();
			else		  _cursor.setPreviousBlock();
		}
		int bk = _cursor.currentBlock;
		int cbx = _cursor.bx , cby = _cursor.by;
		int cb  = getXYBlock( cbx , cby );
		if ( _mouse.button[1] || _mouse.button[2] ) {
			if ( _mouse.button[2] ) bk = B.BLANK;
			switch ( bk ) {
				case B.BLANK:
					if ( cb != B.BLANK ) {
						if ( B.isMatch( cb , B.GOLD ) ) eraseGold( cbx , cby );
						removeXYCharacter( cbx , cby );
						setXYBlock( cbx , cby , B.BLANK );
						_replayEnable = false;
					}
					break;
				case B.BRICK:
				case B.ROCK:
				case B.LADDER:
				case B.ROPE:
				case B.HHOLE:
				case B.HLADDER:
				case B.GOAL:
					if ( !B.isMatch( cb , bk ) ) {
						setXYBlock( cbx , cby , bk );
						if ( B.isMatch( cb , B.GOLD ) ) eraseGold( cbx , cby );
						_replayEnable = false;
					}
					break;
				case B.GOLD:
					if ( !B.isMatch( cb , bk ) ) {
						setXYBlock( cbx , cby , bk );
						_replayEnable = false;
						_goldCnt++;
					}
					break;
				case B.ENEMY:
					if ( !B.isMatch( cb , bk ) ) {
						addEnemy( cbx , cby );
						_replayEnable = false;
					}
					break;
				case B.PLAYER:
					_player.moveTo( cbx * B.WIDTH , cby * B.HEIGHT - 2);
					_replayEnable = false;
					break;
				case B.MBLOCK:
					if ( !B.isMatch( cb , bk ) ) {
						addMBlock( cbx , cby );
						_replayEnable = false;
					}
					break;
				default:
					break;
			}
		}
	} //

	protected void reset() {
		_sysver = GameConfig.SYSVER;
		_mode = MODE_INIT;
		_cols = 15;
		_rows = 15;
		_dat = null;
		_datStr  = "";
		_goldCnt  = 0;
		_frameCnt = 0;
		_diggCnt = 0;
		_killCnt = 0;
		_enemyCnt = 0;
		_mblockCnt = 0;
		_giveupTime = -1;
		_rpCode = "";
		_replayEnable = true;
		_goalPoint.length = 0;
		string[ string ] cfg;
		_cfg = cfg;
		_pad.reset();
		_mouse.reset();
		_enemies.reset();
		_diggers.reset();
		_others.reset();
		_cursor.reset();
		
		_recorder.reset();

		_playerName = GameConfig.playerName;

//		System.gc();
	} //

	public bool loadStage( string text ) {
		Console.writeln( "\nGameStage.loadStage:\n" ~ text );
		reset();
		if ( !super.loadStage( text ) ) 
				return false;

		int gw = _graphics.screenWidth , gh = _graphics.screenHeight;
		float sx = gw * 1f / _vWidth , sy = gh * 1f / _vHeight;
		if ( sx < sy )
			_vHeight = cast(int)round( gh * 1f / sx );
		else
			_vWidth  = cast(int)round( gw * 1f / sy );

		_vWidth     = getParam( "vScreenWidth"  , _vWidth );
		_vHeight    = getParam( "vScreenHeight" , _vHeight );
		_vWidthMax  = getParam( "vWidthMax"   , _vWidth );
		_vHeightMax = getParam( "vHeightMax"  , _vHeight );
		_scrollLeft = Blocks.HALFWIDTH;
		_scrollTop  = Blocks.HALFHEIGHT;
			zoomReset();

		string mp	   = getParam( "mapImg"    , "" );//GameConfig.mapTexture    );
		_playerTexture = getParam( "playerImg" , GameConfig.playerTexture );
		_enemyTexture  = getParam( "enemyImg"  , GameConfig.enemyTexture  );
		if ( mp == "" ) {
			_mapTexture = GameConfig.mapTexture;
			initImage();
		} else
		if ( mp != _mapTexture ) {
			_mapTexture = mp;
			initImage( );
		}

		_missSound     = getParam( "missSound" , GameConfig.missSound );
		_goalSound     = getParam( "goalSound" , GameConfig.goalSound );
		_missSound    = getParam( "missSound" , GameConfig.missSound );
		_gameBgm       = getParam( "gameBgm"   , GameConfig.gameBgm );

		_sysver		= getParam( "sysver"	  , GameConfig.SYSVER );
		_random.set(  getParam( "randomize"   ,  cast(int)std.random.rand() ) );
		_giveupTime = getParam( "giveupTtime" ,  -1 );
		_playerName = getParam( "playerName"  , GameConfig.playerName );
		_title		= getParam( "title"       , "Explode Runner" );
		_cpu		= getParam( "cpu"         , GameConfig.cpu );
		

		createCharacters();

		if ( _rpCode.length > 0 ) {
			_mode = MODE_REPLAY;
			_recorder.data = _rpCode ;
			_pad.reset();
			Console.writeln("rpCode:\n\n%s" , _rpCode );
		} else {
			_mode = MODE_GAME;
		}

		return true;
	} //


	protected bool createCharacters(){
		if( _player is null ) _player = new Player( this , _graphics );
		_player.moveTo( (_cols / 2) * Blocks.WIDTH , (_rows / 2) * Blocks.HEIGHT );
		_enemies.kill();
		_mblocks.kill();
		_diggers.kill();
		_others.kill();
		Console.writeln("\nGameStage.createCharacters");

		for ( int y = 1 ; y < _rows ; y++ ) {
			for ( int x = 1 ; x < _cols ; x++ ) {
				int b = _dat[y][x];
				switch( b ) {
					case Blocks.GOLD:
						_goldCnt++;
					break;
					case Blocks.ENEMY:	// Enemy
						addEnemy( x , y );
					break;
					case Blocks.PLAYER: // Player
						Console.writeln("Set Player %d x %d" , x , y );
						_player.reset();
						_player.cpu = _cpu;
						_player.moveTo( x * Blocks.WIDTH , y * Blocks.HEIGHT );
						setXYBlock( x , y , Blocks.GHOST );
					break;

					case Blocks.GOAL: // Goal
						_goalPoint ~= [x , y];//new Point( x , y);//Point.create( x , y);
						_dat[y][x] = Blocks.BLANK;
					break;


					case Blocks.MBLOCK: // MBlock
						addMBlock( x , y );
						_dat[y][x] = Blocks.BLANK;
					break;
/*
					case 12: // HEnemy
						Console.writeln("Create HEnemy %d x %d" , x , y );
						_dat[y][x] = Blocks.BRICK;
					break;
*/
					default:
					break;
				}

			}
		}
		return true;
	} //

	/**
	*	ロボットの追加
	**/
	public void addEnemy( int xx , int yy ) {
		Enemy e = cast(Enemy) ( _enemies[ _enemyCnt ] );
		if ( e is null ) {
			e = new Enemy( this , _graphics );
			e.id = _enemyCnt++;
			_enemies.add( e );
			Console.writeln( "Create Enemy:ID:%d %d %d x %d" , e.id , _enemyCnt , xx , yy );
		} else
			e.id = _enemyCnt++;
		e.reset();
		e.cpu	 = _cpu;
		e.target = _player ;
		e.moveTo( xx * Blocks.WIDTH , yy * Blocks.HEIGHT );
		setXYBlock( xx , yy , Blocks.GHOST );
		if( _enemyCnt > 0 ) Enemy.initActionTable( _enemyCnt );
	} //

	/**
	*	動かせるブロックの追加
	**/
	public void addMBlock( int xx , int yy ) {
		MovableBlock e = cast(MovableBlock) ( _mblocks[ _mblockCnt ] );
		_mblockCnt++;
		if ( e is null ) {
			e = new MovableBlock( this , _graphics );
			_mblocks.add( e );
		}
		e.reset();
		e.cpu	 = _cpu;
		e.moveTo( xx * Blocks.WIDTH , yy * Blocks.HEIGHT );
		setXYBlock( xx , yy , Blocks.MBLOCK );
	} //
	
	/**
	*	指定のマス目にキャラクターがいたら消す。(どくさいスイッチ)
	**/
	public void removeXYCharacter( int xx , int yy ) {
		alias Blocks B;
		int b = getXYBlock( xx , yy ) , i;
		if ( B.isMatch( b , B.MBLOCK ) ) {
			for ( i = _mblocks.length - 1 ; i >= 0  ; i-- ) {
				if ( _mblocks[i].bx == xx && _mblocks[i].by == yy && _mblocks[i].alive ) {
					_mblocks[i].kill();
					subXYBlockMask( xx , yy , B.MBLOCK );
				}
				
			}
		}
		/*
		if( B.isMatch( b  , B.ENEMY ) ) {
			for ( i = _enemies.length - 1 ; i >= 0  ; i-- ) {
				if ( _enemies[i].bx == xx && _enemies[i].by == yy && _enemies[i].alive ) {
					_enemies[i].kill();
					subXYBlockMask( xx , yy , B.ENEMY );
				}
			}
		}
		*/
	} //


	public void setEnemyMask( int xx , int yy ) {
		addXYBlockMask( xx , yy , Blocks.ENEMY );
	} //

	public void resetEnemyMask( int xx , int yy ) {
		subXYBlockMask( xx , yy , Blocks.ENEMY );
	} //

	public void setDiggerMask( int xx , int yy ) {
		addXYBlockMask( xx , yy , Blocks.DIGGER );
	} //

	public void resetDiggerMask( int xx , int yy ) {
		addXYBlockMask( xx , yy , Blocks.DIGGER );
	} //

	public bool getGold( int xx , int yy , bool lostflag ){
		int b = getXYBlock( xx , yy ) ;
		if ( !Blocks.isGold(b) ) return false;
		if ( lostflag ) {
			_goldCnt--;
			if ( _goldCnt == 0 ) createGoal();
			SoundManager.getSprite().play( _goldSound ).release();
		}
		subXYBlockMask( xx , yy , Blocks.GOLD );
		return true;
	} //

	public bool setGold( int xx , int yy ) {
		int b = getXYBlock( xx , yy );
		if ( Blocks.isNoBlock( b ) ) {
			addXYBlockMask( xx , yy , Blocks.GOLD );
			return true;
		}
		return false;
	} //

	/**
	*	金塊を消す(消失)
	**/
	public void eraseGold( int xx , int yy ) {
		_goldCnt--;
		SoundManager.getSprite().play( _goldSound ).release();
		if ( _goldCnt == 0 ) createGoal();
	} //

	/**
	*	穴を作成
	**/
	public ICharacter setDigger( ICharacter master , int bx , int by ) {
		Digger dig = cast(Digger) _diggers.idle ;
		if ( dig is null ) {
			dig = new Digger( this , _graphics , _texture );
			_diggers.add( dig );
		}
			ISoundSprite s = SoundManager.getSprite().play( _digSound ).release();
		dig.call( bx , by , this , master );
		return dig;
	} //
	
	/**
	*	ブロックを蹴る
	**/
	public bool kickBlock( ICharacter obj , int xx , int yy , int pow ) {
		alias Blocks B;
		int b = getXYBlock( xx , yy ) , i;
		bool ret = true;
		if ( B.isMatch( b , B.MBLOCK ) ) {
			for ( i = _mblocks.length - 1 ; i >= 0  ; i-- ) {
				if ( _mblocks[i].bx == xx && _mblocks[i].by == yy && _mblocks[i].alive )
					ret &= _mblocks[i].onKicked( obj , pow );
			}
		}
		if( B.isMatch( b  , B.ENEMY ) ) {
			for ( i = _enemies.length - 1 ; i >= 0  ; i-- ) {
				if ( _enemies[i].bx == xx && _enemies[i].by == yy && _enemies[i].alive )
					ret &= _enemies[i].onKicked( obj , pow );
			}
		}
		return ret;
	} //

	/**
	*	脱出はしご・ゴール生成
	**/
	public void createGoal() {
		ISoundSprite s = SoundManager.getSprite().play( _ladderSound ).release();
		 foreach ( int i , int[] p ; _goalPoint ) {
			addXYBlockMask( p[0] , p[1] , Blocks.GOAL );
		 }
		 for ( int y = 0 ; y < _rows - 1; y++ ) {
			for ( int x = 1 ; x < _cols-1 ; x++ ) {
				if ( Blocks.isHLadder( _dat[y][x] ) ) {
							addXYBlockMask( x , y , Blocks.LADDER );
							addXYBlockMask( x , y , Blocks.HLADDER );
				}
			}
		 }
		for ( int x = 1 ; x < _cols-1 ; x++ ) {
			setXYBlock( 0 , x , 0  );
		}
	} //


	public string replayCode() {
		alias std.string.toString tos;
		if ( !_replayEnable ) return null;
		string buf[] = [];
		buf ~= [ tos( _frameCnt ) , "," , _cpu , "," , tos( _sysver ) , "\n" ];
		buf ~= [ "[body]\n" , strip( _datStr ) , "\n" ];

		buf ~= [ "[param]\n" ];
		foreach (  string n , v ; _cfg ) {
			buf ~= [ n , "=" , v , "\n" ];
		}
		buf ~= [ "sysver=" , tos( _sysver ) , "\n" ];
		buf ~= [ "randomize=", tos(  _random.seed ) , "\n"];
		buf ~= [ "cpu=", _cpu , "\n"];
		if ( _giveupTime >= 0 ) buf ~= [ "giveupTime=" , tos( _giveupTime ) , "\n"];
		
		if ( _recorder.length > 0 ) {
			buf ~= [ "[replay]\n" , _recorder.data ];
		}
		string ret = "";
		foreach ( string s ; buf ) {
			ret ~= s;
		}
		return ret;
	} //

	public void zoomIn() {
		int vw = _vWidth  - ( _vWidth  >> 5 );
		int vh = _vHeight - ( _vHeight >> 5 );
		if ( vh < 1 << 5 ) return;//vh = 1 << 6;
		if ( vw < 1 << 5 ) return;//vw = 1 << 6;
		_vWidth  = vw;
		_vHeight = vh;
		zoomReset();
	} //

	public void zoomReset() {
		 _scrollLeftMin = Blocks.HALFWIDTH;
		 _scrollTopMin  = Blocks.HALFHEIGHT;
		 _scrollLeftMax =  _width  - Blocks.HALFWIDTH  - _vWidth;
		 _scrollTopMax  =  _height - Blocks.HALFHEIGHT - _vHeight;
		setScrollPoint();
	} //

	public void zoomOut() {
		int vw = _vWidth  + ( _vWidth  >> 5 );
		int vh = _vHeight + ( _vHeight >> 5 );
		if ( vw == _vWidth  ) vw = vw + 4;
		if ( vh == _vHeight ) vh = vh + 4;
		if ( vw > _vWidthMax && vh > _vHeightMax ) {
			vw = _vWidthMax;
			vh = _vHeightMax;
		}
		_vWidth  = vw;
		_vHeight = vh;
		zoomReset();
	} //



	public void setScrollPoint() {
		if ( _player is null ) return;
		super.setScrollPoint( _player );
	} //

} //


public class PreviewStage : BaseStage {
	protected float _centerX , _centerY;
	protected bool _showGrid = false;
	public float z(){return 160;}
	
	public void showGrid( bool v ) {
		_showGrid = v;
	} //
	public bool showGrid() { return _showGrid; }

	public float centerX() {
		return _centerX;
	} //
	public void centerX( float v ) {
		_centerX = v;
	} //
	public float centerY() {
		return _centerY;
	} //
	public void centerY( float v ) {
		_centerY = v;
	} //

	public this( IGraphics g ) {
		super( g );
	} //
	
	public void reset() {
		super.reset();
		_centerX  = 320;
		_centerY  = 100;
	} //

	public void setViewport( IGraphics g ) {
		g.setViewport(	  _scrollLeft
						, _scrollTop
						, _vWidth  + _scrollLeft
						, _vHeight + _scrollTop
						);
	} //

	public bool loadStage( string src ) {
		bool b = super.loadStage( src ) ;
		int gw = _graphics.screenWidth , gh = _graphics.screenHeight;
		float sx = gw * 1f / _vWidth , sy = gh * 1f / _vHeight;
		if ( sx < sy )
			_vHeight = cast(int)round( gh * 1f / sx );
		else
			_vWidth  = cast(int)round( gw * 1f / sy );
		_vWidth     = getParam( "vScreenWidth"  , _vWidth );
		_vHeight    = getParam( "vScreenHeight" , _vHeight );
		_vWidthMax  = getParam( "vWidthMax"   , _vWidth );
		_vHeightMax = getParam( "vHeightMax"  , _vHeight );
		if ( _vWidth > _graphics.screenWidth ) {
			_scrollLeft = Blocks.HALFWIDTH;
		} else
			_scrollLeft = cast(int)round( ( _width - _vWidth ) / 2 );
		if ( _vHeight > _graphics.screenHeight ) {
			_scrollTop  = Blocks.HALFHEIGHT;
		} else
			_scrollTop  = cast(int)round( ( _height- _vHeight ) / 2 );
		return b;
	} //
	public void zoomIn() {
		int vw = _vWidth  - ( _vWidth  >> 5 );
		int vh = _vHeight - ( _vHeight >> 5 );
		if ( vw < ( Blocks.WIDTH * 3 ) ) { // || ( vh < ( Blocks.HEIGHT * 1 ) ) ) {
			return;
		}
		_vWidth  = vw;
		_vHeight = vh;
	} //

	public void zoomOut() {
		int vw = _vWidth  + ( _vWidth  >> 5 );
		int vh = _vHeight + ( _vHeight >> 5 );
		_vWidth  = vw;
		_vHeight = vh;
		_scrollLeft = cast(int)round( ( _width  - vw ) / 2 );
		_scrollTop  = cast(int)round( ( _height - vh ) / 2 );
	} //

	public bool draw( IGraphics g ) {
		alias Blocks B;
		float clipX  = g.viewrect.x  , clipY  = g.viewrect.y , 
			  clipX2 = g.viewrect.x2 , clipY2 = g.viewrect.y2;

		setViewport( g );

		int vw = _vWidth;
		int vh = _vHeight;
		int sx = _scrollLeft >> B.SHIFTX;
		int sy = _scrollTop  >> B.SHIFTY;
		int dw = vw / B.WIDTH  + sx + 2;
		int dh = vh / B.HEIGHT + sy + 2;
		
		if ( dw > _cols ) dw = _cols;
		if ( dh > _rows ) dh = _rows;
		if ( sx < 0 ) sx = 0;
		if ( sy < 0 ) sy = 0;
		Color col = Color.create( 0xffffffff );
		Color bgc = Color.create( 0x88000000 );
		Color gridColor = Color.create( 0x33666666 );
		if ( _frameCnt < 256 / 8 ) {
			col.a = _frameCnt * 8;
		}
		g.drawFilledRect( 0 , 0 , cols * B.WIDTH , rows * B.HEIGHT , bgc );
		for ( int y = sy ; y < dh ; y++ ) {
			for ( int x = sx ; x < dw ; x++ ) {
				if ( B.isDigger( _dat[y][x] ) ) continue;
				int imgIndex = B.imageIndex( _dat[y][x] );
				float drawX = x * B.WIDTH  , drawY = y * B.HEIGHT ;
				if ( imgIndex != 0 ) {
					_texture.frameIndex = imgIndex * 4  + ( ( _frameCnt / 32 ) % 4 ) ;
					g.drawImage( _texture , drawX , drawY , B.WIDTH , B.HEIGHT , col );
				} else
				if ( _showGrid ) {
					g.drawRect( drawX , drawY , B.WIDTH , B.HEIGHT , gridColor );
				}
			}
		}
		
		g.setViewport( clipX , clipY , clipX2 , clipY2);
		return true;
	} //


} //